package ru.averta.notes.service.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.Version;
import org.hibernate.SessionFactory;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ru.averta.notes.domain.Site;
import ru.averta.notes.other.UtilsLucene;
import ru.averta.notes.service.security.SecurityService;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * <code>SubjectLuceneCriteria</code>
 *
 * Преобразует criteria к заполненному luceneQuery
 *
 * @author: fedor.belov (fedor.belov@mail.ru)
 * Date: 06.06.11 21:55
 */

@Component
public class SubjectCriteriaSearchLucene {

    @Autowired private SecurityService security;
    @Autowired private SessionFactory sessionFactory;
    @Autowired private UtilsLucene utilsLucene;

    private static final Logger logger = LoggerFactory.getLogger(SubjectCriteriaSearchLucene.class);

    public FullTextQuery getQueryBySubjectCriteria(SubjectCriteriaBean criteria) {
        return getQueryBySubjectCriteria(criteria, null);
    }

    public FullTextQuery getQueryBySubjectCriteria(SubjectCriteriaBean criteria, Query fullTextQuery) {
        BooleanQuery root = new BooleanQuery();

        loadRequest_userRequest(criteria, root, fullTextQuery);
        loadRequest_currentUser(criteria, root);
        loadRequest_target(criteria, root);
        loadRequest_other(criteria, root);

        return getResult(root);
    }

    /**
     * Возвращает список возможных вариантов поиска без каких-либо значений в исходном запросе
     *
     * @param criteria
     * @return
     */
    public List<SubjectCriteriaExceptionBean> suggestExceptionBeans(SubjectCriteriaSimpleBean criteria) {

        List<SubjectCriteriaExceptionBean> result = new LinkedList<SubjectCriteriaExceptionBean>();

        //Получаем теги. Получаем слова fulltext. Комбинируем различные варианты
        Collection<String> tags = criteria.getFilterTags();
        List<String> words = criteria.getFullTextWords();

        //-1 тег из запроса, если тегов больше 0
        if (tags != null && tags.size() > 0) {
            for (String tag : tags) {
                result.add(new SubjectCriteriaExceptionBean(criteria).setExceptTags(tag));
            }
        }

        //-1 слово из запроса, если их больше 1
        if (words != null && words.size() > 1) {
            for (String word : words) {
                result.add(new SubjectCriteriaExceptionBean(criteria).setExceptWords(word));
            }
        }

        return result;
    }

    /**
     * Возвращает результаты в списке. Элемент списка - объект + document_id
     *
     * @param criteria
     * @param fullTextQuery
     * @return
     */
    public List<Object[]> listWithDocumentIDs(SubjectCriteriaBean criteria, Query fullTextQuery) {
        FullTextQuery q = getQueryBySubjectCriteria(criteria, fullTextQuery);
        q.setProjection(FullTextQuery.THIS, FullTextQuery.DOCUMENT_ID);
        return q.list();
    }

    /**
     * Формирует на основании пользовательского запрос FullTextQuery
     *
     * @param criteria
     * @return
     */
    public Query getFulltextQuery(SubjectCriteriaBean criteria) {
        Query query = null;
        String escapedRequest = utilsLucene.escapeUserRequest(criteria.getFullText());
        if (!escapedRequest.equals("")) {
            Analyzer analyzer = MultiLangAnalyzer.getAnalyzer(escapedRequest);
            String fields[] = {UtilsLucene.FIELD_TITLE, UtilsLucene.FIELD_MARK_CONTENT, UtilsLucene.FIELD_MARK_COMMENT, UtilsLucene.FIELD_IMPORTANT_SEARCH_CONTENT, UtilsLucene.FIELD_FULL_SEARCH_CONTENT};
            BooleanClause.Occur[] flags = {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};
            String fixedRequest = "\"" + escapedRequest + "\"~10";  //Лучше найти больше, чем меньше

            try {
                query = MultiFieldQueryParser.parse(Version.LUCENE_33, fixedRequest, fields, flags, analyzer);
                logger.debug("Результаты обработки пользовательского запроса: " + query.toString());
            } catch (ParseException e) {
                logger.error("Ошибка при обработке запроса " + fixedRequest, e);
            }
        }

        return query;
    }

    private FullTextQuery getResult(Query query) {
        FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
        FullTextQuery hQuery = fullTextSession.createFullTextQuery(query, Site.class);

        return hQuery;
    }

    private void loadRequest_userRequest(SubjectCriteriaBean criteria, BooleanQuery root, Query fullTextQuery) {
        if (fullTextQuery == null && !"".equals(criteria.getFullText())) fullTextQuery = getFulltextQuery(criteria);

        if (fullTextQuery != null) root.add(fullTextQuery, BooleanClause.Occur.MUST);
    }


    private void loadRequest_currentUser(SubjectCriteriaBean criteria, BooleanQuery root) {
        Term userTerm = new Term("user.id", String.valueOf(security.getCurrentUser().getId()));
        root.add(new TermQuery(userTerm), BooleanClause.Occur.MUST);
    }

    private void loadRequest_target(SubjectCriteriaBean criteria, BooleanQuery root) {
        if (criteria.getTarget() == SubjectCriteriaBean.SubjectFilterTarget.All) {
            //Все ок
        } else if (criteria.getTarget() == SubjectCriteriaBean.SubjectFilterTarget.Inbox) {
            //TODO вопрос с inbox?
        }

        Term trashedTerm = null;
        if (criteria.getTarget() == SubjectCriteriaBean.SubjectFilterTarget.Archive) {
            trashedTerm = new Term("archived", "true");
        } else {
            trashedTerm = new Term("archived", "false");
        }
        root.add(new TermQuery(trashedTerm), BooleanClause.Occur.MUST);
    }

    private void loadRequest_other(SubjectCriteriaBean criteria, BooleanQuery root) {
        if (criteria.isFilterFavorite()) {
            Term favoriteTerm = new Term("favorite", "true");
            root.add(new TermQuery(favoriteTerm), BooleanClause.Occur.MUST);
        }

        Collection<String> filterTags = criteria.getFilterTags();
        if (filterTags != null) {
            BooleanQuery tagsQuery = new BooleanQuery();
            for (String tag : filterTags) {
                if (tag != null && !tag.equals("")) {
                    Term tagTerm = new Term("tagConnectors.tag.title", tag);
                    tagsQuery.add(new TermQuery(tagTerm), BooleanClause.Occur.MUST);
                }
            }
            root.add(tagsQuery, BooleanClause.Occur.MUST);
        }
    }

}
